//go:build test_unit

/*
Copyright 2023 The Nuclio Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apigatewayres

import (
	"context"
	"strings"
	"testing"

	"github.com/nuclio/nuclio/pkg/cmdrunner"
	"github.com/nuclio/nuclio/pkg/common"
	"github.com/nuclio/nuclio/pkg/functionconfig"
	"github.com/nuclio/nuclio/pkg/platform"
	nuclioio "github.com/nuclio/nuclio/pkg/platform/kube/apis/nuclio.io/v1beta1"
	"github.com/nuclio/nuclio/pkg/platform/kube/clients/kube"
	"github.com/nuclio/nuclio/pkg/platform/kube/clients/nuclio/clientset/versioned/fake"
	"github.com/nuclio/nuclio/pkg/platform/kube/ingress"
	"github.com/nuclio/nuclio/pkg/platformconfig"

	"github.com/nuclio/logger"
	"github.com/nuclio/zap"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8sfake "k8s.io/client-go/kubernetes/fake"
)

type lazyTestSuite struct {
	suite.Suite
	logger         logger.Logger
	client         Client
	ingressManager *ingress.Manager
	mockCmdRunner  *cmdrunner.MockRunner
}

func (suite *lazyTestSuite) SetupTest() {
	suite.logger, _ = nucliozap.NewNuclioZapTest("test")

	platformConfig, err := platformconfig.NewPlatformConfig("")
	suite.Require().NoError(err)

	suite.mockCmdRunner = cmdrunner.NewMockRunner()

	kubeClientset := k8sfake.NewSimpleClientset()
	suite.ingressManager, err = ingress.NewManager(suite.logger, kube.NewClientWithRetryFromClient(kubeClientset), suite.mockCmdRunner, platformConfig)
	suite.Require().NoError(err)

	suite.client, err = NewLazyClient(suite.logger,
		kube.NewClientWithRetryFromClient(kubeClientset),
		fake.NewSimpleClientset(),
		suite.ingressManager)
	suite.Require().NoError(err)
}

func (suite *lazyTestSuite) TestEnsurePrimaryIngressHasFunctionLabels() {
	for _, testCase := range []struct {
		name                 string
		canaryFunctionConfig *functionconfig.Config
	}{
		{
			name:                 "Primary function should include function-name and canary-function-name labels with canary",
			canaryFunctionConfig: functionconfig.NewConfig(),
		}, {
			name:                 "Primary function should include only function-name label if no canary",
			canaryFunctionConfig: nil,
		},
	} {
		suite.Run(testCase.name, func() {
			primaryFunctionConfig := functionconfig.NewConfig()
			primaryFunctionConfig.Meta.Name = "primary-function-name"
			if testCase.canaryFunctionConfig != nil {
				testCase.canaryFunctionConfig.Meta.Name = "canary-function-name"
			}
			suite.mockCmdRunner.
				On("Run", mock.Anything, mock.IsType(""), mock.Anything).
				Return("echo ehsom | htpasswd -n -i moshe", nil)

			resources, err := suite.client.CreateOrUpdate(context.Background(), suite.getTestAPIGateway(primaryFunctionConfig, testCase.canaryFunctionConfig))
			suite.Require().NoError(err)
			suite.Require().NotNil(resources.IngressResourcesMap())

			primaryIngressResource, canaryIngressResource := suite.getIngressesFromResources(resources)

			// ensure primary ingress has function name
			suite.Require().NotNil(primaryIngressResource)
			suite.Require().NotNil(primaryIngressResource.Ingress)
			suite.Require().NotNil(primaryIngressResource.Ingress.Labels)
			functionNameLabel, exists := primaryIngressResource.Ingress.Labels[common.NuclioResourceLabelKeyFunctionName]
			suite.Require().True(exists, "Expected primary ingress to have function name label")
			suite.Require().Equal(primaryFunctionConfig.Meta.Name, functionNameLabel)
			// ensure primary ingress has canary function name if canary is present
			canaryFunctionNameLabel, exists := primaryIngressResource.Ingress.Labels[common.NuclioResourceLabelKeyCanaryFunctionName]
			if testCase.canaryFunctionConfig != nil {
				suite.Require().True(exists, "Expected primary ingress to have canary function name label")
				suite.Require().Equal(testCase.canaryFunctionConfig.Meta.Name, canaryFunctionNameLabel)

				// ensure canary ingress does not have the labels function name and canary function name
				suite.Require().NotNil(canaryIngressResource)
				suite.Require().NotNil(canaryIngressResource.Ingress)
				suite.Require().NotNil(canaryIngressResource.Ingress.Labels)
				_, exists = canaryIngressResource.Ingress.Labels[common.NuclioResourceLabelKeyFunctionName]
				suite.Require().False(exists, "Expected canary ingress to not have function name label")
				_, exists = canaryIngressResource.Ingress.Labels[common.NuclioResourceLabelKeyCanaryFunctionName]
				suite.Require().False(exists, "Expected canary ingress to not have canary function name label")
			} else {
				suite.Require().False(exists, "Expected primary ingress to not have canary function name label")
			}
		})
	}

}

func (suite *lazyTestSuite) getTestAPIGateway(primaryFunctionConfig, canaryFunctionConfig *functionconfig.Config) nuclioio.NuclioAPIGateway {
	upstreams := []platform.APIGatewayUpstreamSpec{
		{
			Kind: platform.APIGatewayUpstreamKindNuclioFunction,
			NuclioFunction: &platform.NuclioFunctionAPIGatewaySpec{
				Name: primaryFunctionConfig.Meta.Name,
			},
		},
	}

	if canaryFunctionConfig != nil {
		upstreams = append(upstreams, platform.APIGatewayUpstreamSpec{
			Kind: platform.APIGatewayUpstreamKindNuclioFunction,
			NuclioFunction: &platform.NuclioFunctionAPIGatewaySpec{
				Name: canaryFunctionConfig.Meta.Name,
			},
			Percentage: 20,
		})
	}

	return nuclioio.NuclioAPIGateway{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-name",
			Namespace: "test-namespace",
		},

		Spec: platform.APIGatewaySpec{
			Host:               "some-host.com",
			Name:               "test-name",
			AuthenticationMode: ingress.AuthenticationModeBasicAuth,
			Authentication: &platform.APIGatewayAuthenticationSpec{
				BasicAuth: &platform.BasicAuth{
					Username: "moshe",
					Password: "ehsom",
				},
			},
			Upstreams: upstreams,
		},
	}
}

// getIngressesFromResources returns the primary and canary ingress resources from the provided resources
func (suite *lazyTestSuite) getIngressesFromResources(resources Resources) (*ingress.Resources, *ingress.Resources) {
	var primaryIngress, canaryIngress *ingress.Resources

	for resourceName, resource := range resources.IngressResourcesMap() {
		if strings.HasSuffix(resourceName, "-canary") {
			canaryIngress = resource
		} else {
			primaryIngress = resource
		}
	}

	return primaryIngress, canaryIngress
}

func TestLazyTestSuite(t *testing.T) {
	suite.Run(t, new(lazyTestSuite))
}
